Computer languages generally include instructions that contain an op-code and one or more operand addresses. The op-code typically represents a mathematical or logical operation that is to be performed by the instruction (e.g., MOV (move), ADD, STORE and SUB (subtract)). The operands represent a quantity or function that the mathematical or logical operation is to be performed on. For example, a computer instruction of MOV A, B contains the op-code MOV operating on the operands located at memory addresses A and B.
Commonly, a computer language allows operand addresses to be specified using either a direct addressing mode or an indirect addressing mode. In a direct addressing mode, the address specified in the computer instruction is the actual machine address at which is stored the data to be used for the operand. Conversely, in an indirect addressing mode, the address specified in the computer instruction is an address at which is stored an address that specifies the memory location that contains the data that is to be used for the operand.
For example, assume that A represents address location 100 in memory and B represents address location 102 in memory. The instruction MOV A,B uses direct addressing and causes the value stored in address 102 to be moved to address 100. As a further example, assume memory location 102 has a value of 200 stored in it and the instruction MOV A,[B] is executed. The instruction MOV A,[B] uses indirect addressing for the second operand (noted in this example by the brackets [ ] surrounding B) and direct addressing for the first operand (noted in this example by the lack of brackets surrounding A). Thus, the execution of this instruction causes the value stored at memory location 200 to be moved to memory location 100.
In typical database systems, users store, update and retrieve information by submitting commands, or, more generally, statements, to a database application. To be correctly processed, the statements must comply with a database language that is supported by the particular database system. For example, a database application may be configured to support the processing of database queries according to the Oracle-based Programming Language using Structured Query Language (PL/SQL).
Blocks of PL/SQL code (e.g., procedures, functions and packages) can be stored in a compiled format within a database. These compiled PL/SQL blocks can provide several advantages to a database system, such as increased security, increased performance and a reduction in memory requirements. For example, increased security can be provided by granting access to a schema object, such as a table, only through a particular stored procedure. Performance can be increased in a client-server environment since storing procedures within the database can reduce network traffic. Performance may also be increased as the stored procedures are already in a compiled format, and, thus, recompilation is unnecessary.
In addition, memory requirements can be reduced as only a single copy of a stored procedure needs to be stored in a shared area of memory which can be accessed by multiple users. A stored procedure can remain in a shared area of memory throughout a series of invocations by multiple users/applications, thereby avoiding the need to repeatedly retrieve the procedure from disk for each invocation.
In a compiled format, the PL/SQL code blocks specify a series of instructions for a virtual machine. The code blocks can be interpreted by a PL/SQL engine in order to execute the instructions contained in the code blocks. These instructions may contain operand addresses that are comprised of an operand address base pointer, sometimes referred to herein as an "operand base," (e.g., contents of a particular register, a stack frame pointer, etc.), and one or more operand address offsets (sometimes referred to herein as "operand offsets" or "offsets".) For example, in the instruction ADD REG+A,5, the address stored in register REG is the operand address base pointer and A is an operand address offset. Thus, executing this instruction adds 5 to the value stored at the memory address X+A where X is the address stored in register REG. If register REG stores the address 100 and A has a value of 3, executing the instruction ADD REG+A,5 adds 5 to the value stored in memory location 103.
As a further example, consider the instruction MOVC FP+A, DL[4][8]. In this example, the first operand address in the instruction is comprised of an operand address base pointer represented by frame pointer register FP and an operand address offset represented by the variable A. As shown by the lack of brackets, the operand address offset A is specified in the direct addressing mode. Conversely, the second operand address in the instruction is comprised of an operand address base pointer represented by a register DL and two operand address offsets represented by [4] and [8]. As indicated by the brackets, the two operand address offsets [4] and [8] are each specified in the indirect addressing mode. The second operand address is computed as follows:
1) the value stored in register DL is added to 4 to produce a first address; PA1 2) the value stored in memory at the first address is added to 8 to produce a second address; PA1 3) the second operand address is the value stored in memory at the second address.
Typically, instruction formats allocate a fixed amount of space for each operand address offset. The fixed amount of space is determined by the largest possible value for an operand address offset.
For example, FIG. 1 illustrates an instruction 100 in which a typical fixed storage technique is used to allocate a fixed number of bytes (4) for each operand offset. As depicted in FIG. 1, instruction 100 is comprised of an op-code 102, operand bases 104 and 116, operand offsets 138, 140 and 142, and control codes 114, 126 and 136. Control codes 114, 126 and 136 are associated with operand offsets 138, 140 and 142 respectively, and are used to indicate whether a particular operand offset is applied in a direct or indirect addressing mode. In addition, the control codes 114, 126 and 136 are used to indicate whether another operand offset is associated with a particular operand base. Thus, as depicted in FIG. 1, using this fixed storage technique, instruction 100 can be represented using 18 bytes.
However, a drawback associated with allocating a fixed amount of space for each operand offset is that compiled PL/SQL blocks are larger than necessary since many operand offsets require less than the fixed amount of allocated space (this is evident in instruction 100 as operand offset bytes 106, 108, 110, 118, 120, 122, 128, 130 and 132 are all set to zero). Because the compiled PL/SQL blocks are larger than necessary, a fewer number of blocks can be simultaneously resident in the shared memory area allocated for stored PL/SQL blocks. This problem is exacerbated as the shared memory area becomes fragmented, i.e. one or more PL/SQL blocks might have to be removed from the shared area to make room for another PL/SQL block even though the total amount of free (though not contiguous) space available in the shared area is large enough to hold the newly required PL/SQL block. This results in a greater number of retrievals of compiled PL/SQL blocks from disk, and a resultant degradation in database performance.
Another detrimental side effect associated with allocating a fixed amount of space for each operand offset is a reduction in the scalability of the system. Scalability is a measure of the number of concurrent applications that can be resident in memory and executing with satisfactory performance on a system at one time. As the compiled PL/SQL blocks are larger, less of them can be resident in memory at the same time. This results in a reduction in the number of applications that can execute concurrently, since the compiled PL/SQL blocks that are required for the applications wanting to execute may not be able to reside in memory simultaneously.
Another drawback associated with allocating a fixed amount of space for each operand offset is that additional, unnecessary processing is performed on unused memory bytes of an operand offset. Stored operand offsets are typically processed by a PL/SQL engine on a byte by byte basis, in order to generate a corresponding integer value of the operand offset in a "native" (i.e., machine dependent) format that is expected by the machine on which the PL/SQL engine is executing. For example, integers in an PL/SQL virtual machine ("VM") instruction might be stored with the most significant byte first, whereas in a corresponding native instruction integers might be stored in the opposite manner (i.e., with the least significant byte first). In this case, the PL/SQL engine would multiply each byte (of an integer) by an appropriate power of two, and sum the results to produce the same integer for the native instruction format. This can require unnecessary processing if the PL/SQL engine must assume that each operand offset requires a fixed number of bytes (e.g., 4). If an operand offset only requires one non-zero byte, the PL/SQL engine performs three unnecessary multiplications.
Even if the native and PL/SQL VM integer formats are the same, the PL/SQL engine may have to copy the VM formatted operand offsets that are stored at locations that are not byte aligned (as required by the native instruction format) to a new location that is properly byte aligned. Unnecessary copying of unused operand offset bytes is performed if the PL/SQL engine must assume that each operand offset is of a fixed size.
One approach to reducing the wasted space problem is to provide multiple instructions for each op-code (e.g., MOV1, MOV2, MOV3 for the op-code MOV) which accommodate different fixed size operands. By using the instruction (op-code) that uses the smallest fixed size operand offsets large enough to hold the required offset values, the amount of wasted space in an instruction can be reduced. For example, a MOV1 instruction can comprise the op-code MOV1 and two operand offsets that are each one byte in size, and a MOV2 instruction can comprise the op-code MOV2 and two operand offsets that are each two bytes in size.
By using the MOV1 instruction whenever the operand offsets require only one byte each and the MOV2 instruction whenever the operand offsets require two bytes each, the wasted space problem can be reduced. However, a drawback with this approach is that it significantly increases the number of instructions that must be supported by a particular system. For example, if a MOV instruction can have two operand offsets, each comprising up to four bytes in size, the system that is executing the instructions must support 4.sup.2 or sixteen different MOV instructions if it is to support all possible combinations of operand offset sizes. Therefore, this approach may be viable for a RISC like language like JAVA. However, for a database language such as PL/SQL, the number of instructions would quickly become overwhelmingly large as database instructions tend to be longer and more complex and therefore would require many more instruction permutations.
Based on the foregoing, it is desirable to provide a mechanism for reducing the amount of unused space in fields of an instruction that store operand address offsets without having to increase the number of instructions that must be supported by a particular system.